package moa.learners;

import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;

import com.espertech.esper.collection.Pair;

import moa.MOAObject;
import moa.core.Measurement;
import moa.options.FloatOption;
import moa.options.IntOption;
import weka.core.Instance;
import moa.learners.prefix.AlgoPrefixSpan;
import moa.learners.prefix.SequenceDatabase;
import moa.learners.reservoir.AbstractBlackList;
import moa.learners.reservoir.AbstractSlides;
import moa.learners.reservoir.AbstractWriter;
import moa.learners.reservoir.DiskWriter;
import moa.learners.reservoir.IndexedSlides;
import moa.learners.reservoir.SlicedBlackList;

public class Reservoir extends AbstractLearner{

	public String getPurposeString() {
		return "Reservoir";
	}
	
	public IntOption minSupportOption = new IntOption("minSupport", 's',
			"Minimum support threshold.", 50, 0,
			100);

	public FloatOption epsilonOption = new FloatOption("epsilon",
			'e', "Epsilon.", 0.5,0,1);

	public FloatOption deltaOption = new FloatOption(
			"delta", 'd',
			"Delta.", 0.5,0,1);
	
	public IntOption windowSizeOption = new IntOption(
			"windowSize", 'w',
			"Window Size.", 3);
	
	public IntOption savingPeriodOption = new IntOption(
			"savingPeriod", 'p',
			"After reading X savingPeriod instance, prefixSpan is executed.", 1000);

	/*public FileOption nbItemsOption = new FileOption("arffFile", 'f',
			"ARFF file to load.", null, "arff", false);*/
	
	
	private AbstractSlides<HashSet<String>> tanker;
	private AbstractBlackList blacklist;
	private AbstractWriter<HashSet<String>> writer;

	private long countEvents = 0;
	private boolean saveEveryNEvents = true;
	private long savingPeriod = 1000;
	private int numberInstances = 0;
	private Reservoir dogs;
	
	private AlgoPrefixSpan algo;

	
	private SequenceDatabase sequenceDatabase;
	

	/**
	 * @param saveEveryNEvents yes means that the saving time will be based on the number of events processed,
	 * 							false that is has to be based on an external timer
	 */
	public void setPeriodicSave( boolean saveEveryNEvents) {
		this.saveEveryNEvents = saveEveryNEvents;
	}

	/**
	 * @param savingPeriod period between each save, expressed in number of events
	 */
	public void setSavingPeriod(long savingPeriod) {
		this.savingPeriod = savingPeriod;
	}

	/**
	 * @return the current period between each save, expressed in number of events
	 */
	public long getSavingPeriod() {
		return savingPeriod;
	}

	/**
	 * Determines whether it is time for saving the current state of the Reservoir
	 * 
	 * @return true if the Reservoir contents can be dumped after the given time, false otherwise
	 */
	private boolean isTimeToWrite() {
		if(saveEveryNEvents && ++countEvents == savingPeriod) {
			countEvents = 0;
			return true;
		}
		else
			return false;
	}

	public void update(String seqId, String itemSetList){//EventBean[] newEvents) {
		//EventBean event = newEvents[0];

		String seqNumber = seqId ;//event.get("sequenceID").toString();

		// we don't do anything if the sequence is blacklisted
		if( ! blacklist.contains(seqNumber))
		{
			// we store the transaction's items in a HashSet
			String[] tabItemSet = itemSetList.split("-1");//event.get("itemset").toString().split(",");
			HashSet<String> hashedItemSet = new HashSet<String>();
			for( String item : tabItemSet)
				if (!item.equals(" "))
				{
					hashedItemSet.add(item);
					//System.out.println("..." + item);
				}


			// is the sequence already stored?
			if( tanker.hasSequenceNumber(seqNumber)) {
				// yes, we add the new itemset to the window
				// does the window slide?
				if( tanker.addAndSlide( seqNumber, hashedItemSet))
					// yes, we decrement each blacklisted sequence number's counter
					blacklist.decrement();
			}
			else {
				// as long as the reservoir is not full, we just add sequences
				if(tanker.getNbSequences() == tanker.getMaxSequences())
				{
					// no, we retrieve a random number in the range [0,maxSequences[ (total number of sequences 
					int loto = (int) Math.floor(Math.random() * tanker.getMaxSequences());

					// we replace the old sequence by a new one, retrieve its ID and number of elements in the window
					Pair<String, Integer> removedSequence = tanker.replace( loto, seqNumber, hashedItemSet);
					blacklist.add(removedSequence.getFirst(), removedSequence.getSecond());
				}
				else
					tanker.addSequence(seqNumber, hashedItemSet);
			}
		}
		
		if( isTimeToWrite()) {
			try {
				//writer.Write(tanker);
				//writer.WriteSeq(tanker);
				// data
				sequenceDatabase.addSequenceReservoir(tanker);
				
			} catch (Exception e) {
				System.err.println("Couldn't save the actual state of Reservoir");
				e.printStackTrace();
			}
		}
	}
	
	
	
	@Override
	public void getModelDescription(StringBuilder out, int indent) {
		// TODO Auto-generated method stub
		System.out.println("Results");
		algo.printStatistics(sequenceDatabase.size(),out);
	}

	@Override
	protected Measurement[] getModelMeasurementsImpl() {
		System.out.println("getModelMeasurements --> Execution of Algorithm");
		// Create an instance of the algorithm with minsup = 50 %
		algo = new AlgoPrefixSpan(0.01*minSupportOption.getValue()); 
		
		// execute the algorithm
		algo.runAlgorithm(sequenceDatabase);
		return null;
	}

	@Override
	public void resetLearningImpl() {
		double epsilon = new Double(epsilonOption.getValue()); // TO CHOOSE

		double delta = new Double(deltaOption.getValue());// TO CHOOSE

		int cardSd = (int) Math.ceil(Math.log(2/delta) / (2*epsilon*epsilon));
		int winSize = new Integer(windowSizeOption.getValue()); // TO CHOOSE

		System.out.println( "Starting with " + winSize + " itemsets for " + cardSd + " sequences");
		//Reservoir dogs = new Reservoir(cardSd, winSize, "result.arff"); // TO CHOOSE
		// To know when we will store the data

		tanker = new IndexedSlides<HashSet<String>>(cardSd, winSize);

		blacklist = new SlicedBlackList(winSize);

		writer = new DiskWriter<HashSet<String>>( "./test" + "result" + ".arff");
		
		
		setSavingPeriod(savingPeriodOption.getValue());
		
		sequenceDatabase = new SequenceDatabase ();
		
	}

	@Override
	public void trainOnInstanceImpl(Instance inst) {
		// TODO Auto-generated method 
		//System.out.println("inst => " +  inst.toString().replaceAll("'", ""));
		update(Integer.toString(numberInstances), inst.toString().replaceAll("'", ""));

		numberInstances ++;
	}

	@Override
	public MOAObject getModel() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public boolean isRandomizable() {
		// TODO Auto-generated method stub
		return false;
	}

}
